/*
 * Copyright 2018-2021 guerlab.net and other contributors.
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.guerlab.smart.pay.web.alipay;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.extern.slf4j.Slf4j;
import net.guerlab.commons.number.NumberHelper;
import net.guerlab.smart.pay.core.enums.PayStatus;
import net.guerlab.smart.pay.core.exception.AlipayConfigInvalidException;
import net.guerlab.smart.pay.service.entity.AlipayConfig;
import net.guerlab.smart.pay.service.entity.PayLog;
import net.guerlab.smart.pay.service.entity.PayLogNotify;
import net.guerlab.smart.pay.service.service.NotifyOriginalLogService;
import net.guerlab.smart.pay.service.service.PayLogNotifyService;
import net.guerlab.smart.pay.service.service.PayLogService;
import net.guerlab.spring.web.annotation.IgnoreResponseHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 抽象支付宝支付通知控制器
 *
 * @author guer
 */
@Slf4j
public abstract class AbstractAlipayNotifyController extends AbstractAlipayController {

    private static final String TRADE_SUCCESS = "TRADE_SUCCESS";

    private static final String PARAMS_KEY_TRADE_STATUS = "trade_status";

    private PayLogService logService;

    private PayLogNotifyService notifyService;

    private NotifyOriginalLogService notifyOriginalLogService;

    private static boolean rsaCheck(AlipayConfig config, Map<String, String[]> requestParams) {
        if (requestParams == null) {
            return false;
        }

        Map<String, String> params = requestParams.entrySet().stream().filter(AbstractAlipayNotifyController::entryCheck)
                .collect(Collectors.toMap(Map.Entry::getKey, e -> String.join(",", e.getValue())));

        try {
            return AlipaySignature.rsaCheckV1(params, config.getAlipayPublicKey(), AlipayConstants.CHARSET_UTF8,
                    AlipayConstants.SIGN_TYPE_RSA2);
        } catch (AlipayApiException e) {
            log.debug(e.getMessage(), e);
        }

        return false;
    }

    private static boolean entryCheck(Map.Entry<String, String[]> entry) {
        return entry != null && entry.getKey() != null && entry.getValue() != null;
    }

    @Operation(description = "回调通知")
    @PostMapping("/{appId}")
    @IgnoreResponseHandler
    public final String notify(@Parameter(name = "支付宝应用ID", required = true) @PathVariable String appId,
            HttpServletRequest request) {
        String result = notify0(appId, request);
        notifyOriginalLogService.add(getPayChannel(), request.getParameterMap(), result);
        return result;
    }

    protected final String notify0(String appId, HttpServletRequest request) {
        if (!TRADE_SUCCESS.equals(request.getParameter(PARAMS_KEY_TRADE_STATUS))) {
            return "ignore trade status";
        }
        AlipayConfig config = configService.selectByIdOptional(appId).orElseThrow(AlipayConfigInvalidException::new);

        if (!rsaCheck(config, request.getParameterMap())) {
            return "sign invalid";
        }

        long payLogId;
        try {
            payLogId = Long.parseLong(request.getParameter("out_trade_no"));
        } catch (Exception e) {
            return e.getLocalizedMessage();
        }

        BigDecimal amount = new BigDecimal(request.getParameter("total_amount"));
        String notifyAppId = request.getParameter("app_id");
        if (!appId.equals(notifyAppId)) {
            return "appId not equals";
        }

        PayLog payLog = logService.selectById(payLogId);

        if (payLog == null) {
            return "payLog invalid";
        } else if (!Objects.equals(getPayChannel(), payLog.getPayChannel())) {
            return "payLog payChannel error, need: " + payLog.getPayChannel() + ", input: " + getPayChannel();
        } else if (!NumberHelper.isEquals(amount, payLog.getAmount())) {
            return "payLog amount error, need: " + payLog.getAmount() + ", input: " + amount;
        } else if (!appId.equals(payLog.getExtend().get(KEY_APP_ID))) {
            return "payLog appId error, need: " + payLog.getExtend() + ", input: " + appId;
        } else if (payLog.getPayStatus() != PayStatus.WAIT_PAY) {
            return "payLog status error: " + payLog.getPayStatus();
        }

        PayLogNotify payLogNotify = PayLogNotify.buildByPayLog(payLog);
        payLogNotify.setOutOrderSn(request.getParameter("trade_no"));

        try {
            notifyService.insert(payLogNotify);
        } catch (Exception e) {
            log.debug(e.getLocalizedMessage(), e);
        }

        logService.payed(payLog);

        return "success";
    }

    @Autowired
    public void setLogService(PayLogService logService) {
        this.logService = logService;
    }

    @Autowired
    public void setNotifyService(PayLogNotifyService notifyService) {
        this.notifyService = notifyService;
    }

    @Autowired
    public void setNotifyOriginalLogService(NotifyOriginalLogService notifyOriginalLogService) {
        this.notifyOriginalLogService = notifyOriginalLogService;
    }
}
